package cn.ywyself.extend.utils;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * 二维码生成工具
 *
 * @author : ywyself
 * @created : 2018-10-12 8:33
 */
public class QrCodeUtils {
    private static final int COLOR_BLACK = 0xFF000000;
    private static final int COLOR_WHITE = 0xFFFFFFFF;

    private static MultiFormatWriter multiFormatWriter = new MultiFormatWriter();

    /**
     * 二维码矩阵
     */
    private BitMatrix bitMatrix = null;

    /**
     * 二维码内容
     */
    private String text = null;
    /**
     * 二维码大小
     */
    private int size = 250;
    /**
     * 中心logo图像地址
     */
    private String logoImagePath = null;
    /**
     * 背景图地址
     */
    private String backImagePath = null;

    /**
     * 初始化
     *
     * @return 当前对象
     */
    public static QrCodeUtils init() {
        return new QrCodeUtils();
    }

    /**
     * 二维码内容
     */
    public QrCodeUtils setText(String text) {
        this.text = text;
        return this;
    }

    /**
     * 二维码大小
     */
    public QrCodeUtils setSize(int size) {
        this.size = size;
        return this;
    }

    /**
     * 设置中心logo图像地址
     */
    public QrCodeUtils setLogoImagePath(String logoImagePath) {
        this.logoImagePath = logoImagePath;
        return this;
    }

    /**
     * 设置背景图地址
     */
    public QrCodeUtils setBackImagePath(String backImagePath) {
        this.backImagePath = backImagePath;
        return this;
    }

    /**
     * 二维码写入到输出流中
     *
     * @param outputStream 输出流
     * @throws WriterException 写入异常
     * @throws IOException     IO异常
     */
    public void write(OutputStream outputStream) throws WriterException, IOException {
        if (this.bitMatrix == null) {
            create(this.text, this.size);
        }
        ImageIO.write(buildBufferedImage(bitMatrix), "png", outputStream);
    }

    /**
     * 二维码写入到文件中
     *
     * @param file 文件
     * @throws WriterException 写入异常
     * @throws IOException     IO异常
     */
    public void save(File file) throws WriterException, IOException {
        if (file == null) {
            throw new IllegalArgumentException("param of file can not be null");
        }
        if (file.isDirectory()) {
            throw new IllegalArgumentException("param of file must be a file object");
        }
        String fileName = file.getName();
        if ("".equals(fileName.trim())) {
            throw new IllegalArgumentException("file error");
        }
        String ext;
        int index = file.getName().lastIndexOf(".");
        if (index == -1) {
            throw new IllegalArgumentException("file do not has");
        } else {
            ext = file.getName().substring(index + 1);
        }
        // 扩展名中不能包含路径相关的符号
        if (ext.contains(File.pathSeparator)) {
            return;
        }
        if (this.bitMatrix == null) {
            create(this.text, this.size);
        }
        ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(file);
        ImageIO.write(buildBufferedImage(this.bitMatrix), ext, imageOutputStream);
        imageOutputStream.close();
    }

    /**
     * 二维码生成
     *
     * @param text 二维码内容
     * @param size 二维码大小
     * @throws WriterException 写入异常
     */
    private void create(String text, int size) throws WriterException {
        if (null == text || "".equals(text.trim())) {
            throw new IllegalArgumentException("param of text can not be blank");
        }
        if (size < 1) {
            throw new IllegalArgumentException("param of size is illegal");
        }
        Map<EncodeHintType, Object> hintType = new HashMap<>();
        hintType.put(EncodeHintType.CHARACTER_SET, "utf-8");
        // 设置容错等级
        hintType.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        // 设置白边框宽度
        hintType.put(EncodeHintType.MARGIN, 1);
        // 生成二维码
        this.bitMatrix = multiFormatWriter.encode(text, BarcodeFormat.QR_CODE, size, size, hintType);
    }

    /**
     * 将矩阵转换为图像buffer，可以设置logo和背景图
     *
     * @param bitMatrix 二维码矩阵
     * @return 二维码buffer对象
     * @throws IOException IO异常
     */
    private BufferedImage buildBufferedImage(BitMatrix bitMatrix) throws IOException {
        int width = bitMatrix.getWidth();
        int height = bitMatrix.getHeight();
        // 写入QrCode
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, bitMatrix.get(x, y) ? COLOR_BLACK : COLOR_WHITE);
            }
        }
        // 写入背景图
        if (null != this.backImagePath && !"".equals(backImagePath.trim())) {
            BufferedImage bgImg = scaleImage(backImagePath, width, height);
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++) {
                    // 从背景图取出颜色填充到
                    if (!bitMatrix.get(x, y)) {
                        image.setRGB(x, y, bgImg.getRGB(x, y));
                    }
                }
            }
        }
        // 写入logo
        if (null != logoImagePath && !"".equals(logoImagePath.trim())) {
            BufferedImage logoImg = scaleImage(logoImagePath, width / 4, height / 4);
            for (int x = 0; x < logoImg.getWidth(); x++) {
                for (int y = 0; y < logoImg.getHeight(); y++) {
                    int offsetX = x + image.getWidth() / 2 - logoImg.getWidth() / 2;
                    int offsetY = y + image.getHeight() / 2 - logoImg.getHeight() / 2;
                    // 读取logo对应点的颜色
                    int color = logoImg.getRGB(x, y);
                    // 设置到qrCode
                    image.setRGB(offsetX, offsetY, color);
                }
            }
        }
        return image;
    }

    /**
     * 读取图片，并缩放到指定大小，当宽度或高度为-1时：不进行缩放
     *
     * @param filePath 文件在系统中的路径
     * @param width    缩放后的宽度
     * @param height   缩放后的高度
     * @return 目标图像 buffer
     * @throws IOException IO异常
     */
    private BufferedImage scaleImage(String filePath, int width, int height) throws IOException {
        // 读取文件
        File imageFile = new File(filePath);
        // 源图像
        final BufferedImage srcImg = ImageIO.read(imageFile);
        // 定义源图像的[宽度][高度]
        int srcWidth = srcImg.getWidth();
        int srcHeight = srcImg.getHeight();
        // 定义目标图像的[宽度][高度]
        int destWidth = srcWidth;
        // 定义目标图像的高度
        int destHeight = srcHeight;
        if (destHeight != -1) {
            destHeight = height;
        }
        if (width != -1) {
            destWidth = width;
        }
        // 目标图片的大小和源图片的大小一致时，返回源图片
        if (destWidth == srcWidth && destHeight == srcHeight) {
            return srcImg;
        }
        // 目标图像
        final Image destImage;
        // 缩放模式，默认模式
        int scaleType = Image.SCALE_DEFAULT;
        if (destWidth > srcWidth || destHeight > srcHeight) {
            // 放大图片使用平滑模式
            scaleType = Image.SCALE_SMOOTH;
        }
        // 根据源图片尺寸缩放图片
        destImage = srcImg.getScaledInstance(destWidth, destHeight, scaleType);
        // 返回buffer
        if (destImage instanceof BufferedImage) {
            return (BufferedImage) destImage;
        } else {
            BufferedImage destBufferedImage = new BufferedImage(destImage.getWidth(null), destImage.getHeight(null), BufferedImage.TYPE_INT_RGB);
            Graphics2D graphics = destBufferedImage.createGraphics();
            graphics.drawImage(destImage, 0, 0, null);
            graphics.dispose();
            return destBufferedImage;
        }
    }
}
